﻿//BasketTrader.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo
{
	using System;
	using System.Collections.Generic;
	using System.Linq;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.Serialization;

	using StockSharp.Logging;
	using StockSharp.Algo.Testing;
	using StockSharp.BusinessEntities;

	/// <summary>
	/// Шлюз-агрегатор, позволяющий оперировать одновременно несколькими шлюзами, подключенных к разным торговым системам.
	/// </summary>
	public class BasketTrader : BaseLogReceiver<ITrader>, ITrader
	{
		/// <summary>
		/// Интерфейс, описывающий список шлюзов к торговым системам, с которыми оперирует агрегатор.
		/// </summary>
		public interface IInnerTraderList : INotifyList<ITrader>, ISynchronizedCollection<ITrader>
		{
			/// <summary>
			/// Внутренние шлюзы, отсортированные по скорости работы.
			/// </summary>
			IEnumerable<ITrader> SortedTraders { get; }

			/// <summary>
			/// Индексатор, через который задаются приоритеты скорости (чем меньше значение, те быстрее шлюз) на внутренние шлюзы.
			/// </summary>
			/// <param name="trader">Внутренний шлюз.</param>
			/// <returns>Приоритет шлюза. Если задается значение -1, то шлюз считается выключенным.</returns>
			int this[ITrader trader] { get; set; }
		}

		private sealed class InnerTraderList : CachedSynchronizedList<ITrader>, IInnerTraderList
		{
			private readonly Dictionary<Portfolio, ITrader> _portfolioTraders = new Dictionary<Portfolio, ITrader>();
 
			private readonly BasketTrader _parent;
			
			public InnerTraderList(BasketTrader parent)
			{
				if (parent == null)
					throw new ArgumentNullException("parent");

				_parent = parent;
			}

			public ITrader GetTrader(Portfolio portfolio)
			{
				if (portfolio == null)
					throw new ArgumentNullException("portfolio");

				lock (SyncRoot)
				{
					return _portfolioTraders.SafeAdd(portfolio, pf =>
					{
						var trader = SortedTraders.FirstOrDefault(t => t.Portfolios.Contains(pf));

						if (trader == null)
							throw new InvalidOperationException("Шлюз для портфеля {0} не найден.".Put(pf.Name));

						return trader;
					});
				}
			}

			public IEnumerable<ITrader> SortedTraders
			{
				get { return Cache.Where(t => this[t] != -1).OrderBy(t => this[t]); }
			}

			public IEnumerable<Trade> Trades
			{
				get
				{
					return Cache.SelectMany(t => t.Trades);
				}
			}

			public IEnumerable<ExchangeBoard> ExchangeBoards
			{
				get
				{
					return Cache.SelectMany(t => t.ExchangeBoards);
				}
			}

			public IEnumerable<Security> Securities
			{
				get
				{
					return Cache.SelectMany(t => t.Securities);
				}
			}

			public IEnumerable<Portfolio> Portfolios
			{
				get
				{
					return Cache.SelectMany(t => t.Portfolios);
				}
			}

			protected override bool OnAdding(ITrader item)
			{
				Subscribe(item);
				return base.OnAdding(item);
			}

			protected override void OnAdded(ITrader item)
			{
				base.OnAdded(item);
				ProcessTraderValues(item);
			}

			protected override bool OnInserting(int index, ITrader item)
			{
				Subscribe(item);
				return base.OnInserting(index, item);
			}

			protected override void OnInserted(int index, ITrader item)
			{
				base.OnInserted(index, item);
				ProcessTraderValues(item);
			}

			protected override bool OnRemoving(ITrader item)
			{
				UnSubscribe(item);
				return base.OnRemoving(item);
			}

			protected override bool OnClearing()
			{
				lock (SyncRoot)
					ForEach(UnSubscribe);

				return base.OnClearing();
			}

			private void Subscribe(ITrader trader)
			{
				if (trader == null)
					throw new ArgumentNullException("trader");

				trader.DoIf<ITrader, BaseTrader>(bt => bt.Parent = _parent);

				trader.NewPortfolios += portfolios => _parent.OnInnerTraderNewPortfolios(trader, portfolios);
				trader.PortfoliosChanged += portfolios => _parent.OnInnerTraderPortfoliosChanged(trader, portfolios);
				trader.NewPositions += positions => _parent.OnInnerTraderNewPositions(trader, positions);
				trader.PositionsChanged += positions => _parent.OnInnerTraderPositionsChanged(trader, positions);
				trader.NewSecurities += securities => _parent.OnInnerTraderNewSecurities(trader, securities);
				trader.SecuritiesChanged += securities => _parent.OnInnerTraderSecuritiesChanged(trader, securities);
				trader.NewTrades += trades => _parent.OnInnerTraderNewTrades(trader, trades);
				trader.NewMyTrades += trades => _parent.OnInnerTraderNewMyTrades(trader, trades);
				trader.NewOrders += orders => _parent.OnInnerTraderNewOrders(trader, orders);
				trader.OrdersChanged += orders => _parent.OnInnerTraderOrdersChanged(trader, orders);
				trader.OrdersRegisterFailed += fails => _parent.OnInnerTraderOrdersRegisterFailed(trader, fails);
				trader.OrdersCancelFailed += fails => _parent.OnInnerTraderOrdersCancelFailed(trader, fails);
				trader.NewStopOrders += orders => _parent.OnInnerTraderNewStopOrders(trader, orders);
				trader.StopOrdersChanged += orders => _parent.OnInnerTraderStopOrdersChanged(trader, orders);
				trader.StopOrdersRegisterFailed += fails => _parent.OnInnerTraderStopOrdersRegisterFailed(trader, fails);
				trader.StopOrdersCancelFailed += fails => _parent.OnInnerTraderStopOrdersCancelFailed(trader, fails);
				trader.NewMarketDepths += depths => _parent.OnInnerTraderNewMarketDepths(trader, depths);
				trader.MarketDepthsChanged += depths => _parent.OnInnerTraderMarketDepthsChanged(trader, depths);
				trader.NewOrderLogItems += items => _parent.OnInnerTraderNewOrderLogItems(trader, items);
				trader.Connected += () => _parent.OnInnerTraderConnected(trader);
				trader.Disconnected += () => _parent.OnInnerTraderDisconnected(trader);
				trader.ConnectionError += error => _parent.OnInnerTraderConnectionError(trader, error);
				trader.ProcessDataError += error => _parent.OnInnerTraderProcessDataError(trader, error);
				trader.NewDataExported += () => _parent.OnInnerTraderNewDataExported(trader);

				if (Count == 0)
					trader.MarketTimeChanged += diff => _parent.OnInnerTraderMarketTimeChanged(trader, diff);

				_enables.Add(trader, 0);
			}

			public void UnSubscribe(ITrader trader)
			{
				if (trader == null)
					throw new ArgumentNullException("trader");

				//trader.NewPortfolios -= _parent.OnInnerTraderNewPortfolios;
				//trader.PortfoliosChanged -= _parent.OnInnerTraderPortfoliosChanged;
				//trader.NewPositions -= _parent.OnInnerTraderNewPositions;
				//trader.PositionsChanged -= _parent.OnInnerTraderPositionsChanged;
				//trader.NewSecurities -= _parent.OnInnerTraderNewSecurities;
				//trader.SecuritiesChanged -= _parent.OnInnerTraderSecuritiesChanged;
				//trader.NewTrades -= _parent.OnInnerTraderNewTrades;
				//trader.NewMyTrades -= _parent.OnInnerTraderNewMyTrades;
				//trader.NewOrders -= _parent.OnInnerTraderNewOrders;
				//trader.OrdersChanged -= _parent.OnInnerTraderOrdersChanged;
				//trader.OrdersRegisterFailed -= _parent.OnInnerTraderOrdersRegisterFailed;
				//trader.OrdersCancelFailed -= _parent.OnInnerTraderOrdersCancelFailed;
				//trader.NewStopOrders -= _parent.OnInnerTraderNewStopOrders;
				//trader.StopOrdersChanged -= _parent.OnInnerTraderStopOrdersChanged;
				//trader.StopOrdersRegisterFailed -= _parent.OnInnerTraderStopOrdersRegisterFailed;
				//trader.StopOrdersCancelFailed -= _parent.OnInnerTraderStopOrdersCancelFailed;
				//trader.NewMarketDepths -= _parent.OnInnerTraderNewMarketDepths;
				//trader.MarketDepthsChanged -= _parent.OnInnerTraderMarketDepthsChanged;
				//trader.NewOrderLogItems -= _parent.OnInnerTraderNewOrderLogItems;
				//trader.Connected -= _parent.OnInnerTraderConnected;
				//trader.Disconnected -= _parent.OnInnerTraderDisconnected;
				//trader.ConnectionError -= _parent.OnInnerTraderConnectionError;
				//trader.ProcessDataError -= _parent.OnInnerTraderProcessDataError;
				//trader.NewDataExported -= _parent.OnInnerTraderNewDataExported;
				//trader.MarketTimeChanged -= _parent.OnInnerTraderMarketTimeChanged;

				_enables.Remove(trader);

				foreach (var portfolio in trader.Portfolios)
				{
					_portfolioTraders.Remove(portfolio);
				}

				var baseTrader = trader as BaseTrader;

				if (baseTrader != null)
					baseTrader.Parent = null;
			}

			private void ProcessTraderValues(ITrader trader)
			{
				if (trader == null)
					throw new ArgumentNullException("trader");

				_parent.OnInnerTraderNewPortfolios(trader, trader.Portfolios);
				_parent.OnInnerTraderNewSecurities(trader, trader.Securities);
			}

			private readonly Dictionary<ITrader, int> _enables = new Dictionary<ITrader, int>(); 
			
			public int this[ITrader trader]
			{
				get
				{
					lock (SyncRoot)
						return _enables.TryGetValue2(trader) ?? -1;
				}
				set
				{
					if (value < -1)
						throw new ArgumentOutOfRangeException();

					lock (SyncRoot)
					{
						if (!Contains(trader))
							return;

						_enables[trader] = value;
						_portfolioTraders.Clear();
					}
				}
			}
		}

		/// <summary>
		/// Создать шлюз-агрегатор.
		/// </summary>
		public BasketTrader()
		{
			_innerTraders = new InnerTraderList(this);
			SupportTradesUnique = true;
			SetChilds(_innerTraders);
		}

		/// <summary>
		/// Поддерживать ли уникальность данных в пределах всех вложенных шлюзов <see cref="InnerTraders"/>.
		/// Например, если один и тот же инструмент транслируется из нескольких вложенных шлюзов <see cref="InnerTraders"/>,
		/// то событие <see cref="NewSecurities"/> будет вызвано только один раз. По-умолчанию режим выключен.
		/// </summary>
		public bool SupportUnique { get; set; }

		/// <summary>
		/// Поддерживать ли уникальность сделок в пределах всех вложенных шлюзов <see cref="InnerTraders"/>. По-умолчанию режим включен.
		/// </summary>
		public bool SupportTradesUnique { get; set; }

		private readonly InnerTraderList _innerTraders;

		/// <summary>
		/// Шлюзы к торговым системам, с которыми оперирует агрегатор.
		/// </summary>
		public IInnerTraderList InnerTraders
		{
			get { return _innerTraders; }
		}

		private readonly CachedSynchronizedSet<ExchangeBoard> _exchanges = new CachedSynchronizedSet<ExchangeBoard>();

		/// <summary>
		/// Список всех биржевых площадок, для которых загружены инструменты <see cref="ITrader.Securities"/>.
		/// </summary>
		public IEnumerable<ExchangeBoard> ExchangeBoards
		{
			get
			{
				return SupportUnique ? _exchanges.Cache : _innerTraders.ExchangeBoards;
			}
		}

		private readonly CachedSynchronizedDictionary<string, Security> _securities = new CachedSynchronizedDictionary<string, Security>();

		/// <summary>
		/// Список всех загруженных инструментов.
		/// Вызывать необходимо после того, как пришло событие <see cref="E:StockSharp.BusinessEntities.ITrader.NewSecurities"/>.
		/// Иначе будет возвращено постое множество.
		/// </summary>
		public virtual IEnumerable<Security> Securities
		{
			get
			{
				return SupportUnique ? _securities.CachedValues : _innerTraders.Securities;
			}
		}

		/// <summary>
		/// Получить все заявки.
		/// </summary>
		public virtual IEnumerable<Order> Orders
		{
			get { return _innerTraders.Cache.SelectMany(t => t.Orders); }
		}

		/// <summary>
		/// Получить все стоп-заявки.
		/// </summary>
		public virtual IEnumerable<Order> StopOrders
		{
			get { return _innerTraders.Cache.SelectMany(t => t.StopOrders); }
		}

		/// <summary>
		/// Получить все ошибки при регистрации заявок.
		/// </summary>
		public virtual IEnumerable<OrderFail> OrderRegisterFails
		{
			get { return _innerTraders.Cache.SelectMany(t => t.OrderRegisterFails); }
		}

		/// <summary>
		/// Получить все ошибки при снятии заявок.
		/// </summary>
		public virtual IEnumerable<OrderFail> OrderCancelFails
		{
			get { return _innerTraders.Cache.SelectMany(t => t.OrderCancelFails); }
		}

		private readonly SynchronizedDictionary<Security, SynchronizedDictionary<long, Trade>> _trades = new SynchronizedDictionary<Security, SynchronizedDictionary<long, Trade>>();
		
		/// <summary>
		/// Получить все сделки.
		/// </summary>
		public virtual IEnumerable<Trade> Trades
		{
			get
			{
				return SupportTradesUnique
							? _trades.SyncGet(d => d.Values.SelectMany(d2 => d2.Values).ToArray())
							: _innerTraders.Trades;
			}
		}

		/// <summary>
		/// Получить все собственные сделки.
		/// </summary>
		public virtual IEnumerable<MyTrade> MyTrades
		{
			get { return _innerTraders.Cache.SelectMany(t => t.MyTrades); }
		}

		private readonly CachedSynchronizedDictionary<string, Portfolio> _portfolios = new CachedSynchronizedDictionary<string, Portfolio>();

		/// <summary>
		/// Получить все портфели.
		/// </summary>
		public virtual IEnumerable<Portfolio> Portfolios
		{
			get
			{
				return SupportUnique ? _portfolios.CachedValues : _innerTraders.Portfolios;
			}
		}

		/// <summary>
		/// Получить все позиции.
		/// </summary>
		public virtual IEnumerable<Position> Positions
		{
			get { return _innerTraders.Cache.SelectMany(t => t.Positions); }
		}

		/// <summary>
		/// Проверить соединение.
		/// </summary>
		public virtual bool IsConnected
		{
			get
			{
				var traders = _innerTraders.SortedTraders;
				return !traders.IsEmpty() && traders.All(t => t.IsConnected);
			}
		}

		/// <summary>
		/// Запущен ли экспорт. Экспорт запускается через метод <see cref="StartExport"/>.
		/// </summary>
		public virtual bool IsExportStarted
		{
			get
			{
				var traders = _innerTraders.SortedTraders;
				return !traders.IsEmpty() && traders.All(t => t.IsExportStarted);
			}
		}

		/// <summary>
		/// Поддерживается ли перерегистрация заявок через метод <see cref="ITrader.ReRegisterOrder(StockSharp.BusinessEntities.Order,StockSharp.BusinessEntities.Order)"/>
		/// в виде одной транзакции.
		/// </summary>
		public virtual bool IsSupportAtomicReRegister
		{
			get
			{
				var traders = _innerTraders.SortedTraders;
				return !traders.IsEmpty() && traders.All(t => t.IsSupportAtomicReRegister);
			}
		}

		/// <summary>
		/// Список всех инструментов, зарегистрированных через <see cref="ITrader.RegisterSecurity"/>.
		/// </summary>
		public virtual IEnumerable<Security> RegisteredSecurities
		{
			get { return _innerTraders.SortedTraders.SelectMany(t => t.RegisteredSecurities); }
		}

		/// <summary>
		/// Список всех инструментов, зарегистрированных через <see cref="ITrader.RegisterMarketDepth"/>.
		/// </summary>
		public virtual IEnumerable<Security> RegisteredMarketDepths
		{
			get { return _innerTraders.SortedTraders.SelectMany(t => t.RegisteredMarketDepths); }
		}

		/// <summary>
		/// Список всех инструментов, зарегистрированных через <see cref="ITrader.RegisterTrades"/>.
		/// </summary>
		public virtual IEnumerable<Security> RegisteredTrades
		{
			get { return _innerTraders.SortedTraders.SelectMany(t => t.RegisteredTrades); }
		}

		/// <summary>
		/// Список всех инструментов, зарегистрированных через <see cref="ITrader.RegisterOrderLog"/>.
		/// </summary>
		public virtual IEnumerable<Security> RegisteredOrderLogs
		{
			get { return _innerTraders.SortedTraders.SelectMany(t => t.RegisteredOrderLogs); }
		}

		/// <summary>
		/// Список всех портфелей, зарегистрированных через <see cref="ITrader.RegisterPortfolio"/>.
		/// </summary>
		public virtual IEnumerable<Portfolio> RegisteredPortfolios
		{
			get { return _innerTraders.SortedTraders.SelectMany(t => t.RegisteredPortfolios); }
		}

		/// <summary>
		/// Текстовое описание шлюза.
		/// </summary>
		public string DisplayName
		{
			get { return _innerTraders.SortedTraders.Select(t => t.DisplayName).Join(", "); }
		}

		/// <summary>
		/// Событие появления собственных новых сделок.
		/// </summary>
		public event Action<IEnumerable<MyTrade>> NewMyTrades;

		/// <summary>
		/// Событие появления всех новых сделок.
		/// </summary>
		public event Action<IEnumerable<Trade>> NewTrades;

		/// <summary>
		/// Событие появления новых заявок.
		/// </summary>
		public event Action<IEnumerable<Order>> NewOrders;

		/// <summary>
		/// Событие изменения состояния заявок (снята, удовлетворена).
		/// </summary>
		public event Action<IEnumerable<Order>> OrdersChanged;

		/// <summary>
		/// Событие ошибок при регистрации заявок.
		/// </summary>
		public event Action<IEnumerable<OrderFail>> OrdersRegisterFailed;

		/// <summary>
		/// Событие ошибок при снятии заявок.
		/// </summary>
		public event Action<IEnumerable<OrderFail>> OrdersCancelFailed;

		/// <summary>
		/// Событие ошибок при регистрации стоп-заявок.
		/// </summary>
		public event Action<IEnumerable<OrderFail>> StopOrdersRegisterFailed;

		/// <summary>
		/// Событие ошибок при снятии стоп-заявок.
		/// </summary>
		public event Action<IEnumerable<OrderFail>> StopOrdersCancelFailed;

		/// <summary>
		/// Событие появления новых стоп-заявок.
		/// </summary>
		public event Action<IEnumerable<Order>> NewStopOrders;

		/// <summary>
		/// Событие изменения состояния стоп-заявок.
		/// </summary>
		public event Action<IEnumerable<Order>> StopOrdersChanged;

		/// <summary>
		/// Событие появления новых инструментов.
		/// </summary>
		public event Action<IEnumerable<Security>> NewSecurities;

		/// <summary>
		/// Событие изменения параметров инструментов.
		/// </summary>
		public event Action<IEnumerable<Security>> SecuritiesChanged;

		/// <summary>
		/// Событие появления новых портфелей.
		/// </summary>
		public event Action<IEnumerable<Portfolio>> NewPortfolios;

		/// <summary>
		/// Событие изменения параметров портфелей.
		/// </summary>
		public event Action<IEnumerable<Portfolio>> PortfoliosChanged;

		/// <summary>
		/// Событие появления новых позиций.
		/// </summary>
		public event Action<IEnumerable<Position>> NewPositions;

		/// <summary>
		/// Событие изменения параметров позиций.
		/// </summary>
		public event Action<IEnumerable<Position>> PositionsChanged;

		/// <summary>
		/// Событие появления новых стаканов с котировками.
		/// </summary>
		public event Action<IEnumerable<MarketDepth>> NewMarketDepths;

		/// <summary>
		/// Событие изменения стаканов с котировками.
		/// </summary>
		public event Action<IEnumerable<MarketDepth>> MarketDepthsChanged;

		/// <summary>
		/// Событие появления новых записей в логе заявок.
		/// </summary>
		public event Action<IEnumerable<OrderLogItem>> NewOrderLogItems;

		/// <summary>
		/// Событие успешного подключения.
		/// </summary>
		public event Action Connected;

		/// <summary>
		/// Событие успешного отключения.
		/// </summary>
		public event Action Disconnected;

		/// <summary>
		/// Событие ошибки подключения (например, соединения было разорвано).
		/// </summary>
		public event Action<Exception> ConnectionError;

		/// <summary>
		/// Событие, сигнализирующее о новых экспортируемых данных.
		/// </summary>
		public event Action NewDataExported;

		/// <summary>
		/// Событие, сигнализирующее об ошибке при получении или обработке новых данных с сервера.
		/// </summary>
		public event Action<Exception> ProcessDataError;

		/// <summary>
		/// Событие, сигнализирующее об изменении текущего времени на биржевых площадках <see cref="ITrader.ExchangeBoards"/>.
		/// Передается разница во времени, прошедшее с последнего вызова события. Первый раз событие передает значение <see cref="TimeSpan.Zero"/>.
		/// </summary>
		public event Action<TimeSpan> MarketTimeChanged;

		/// <summary>
		/// Получить биржевое время.
		/// </summary>
		/// <param name="exchange">Биржа.</param>
		/// <returns>Биржевое время.</returns>
		public DateTime GetMarketTime(Exchange exchange)
		{
			if (exchange == null)
				throw new ArgumentNullException("exchange");

			var trader = GetSortedTraders().FirstOrDefault(t => t.ExchangeBoards.Any(b => b.Exchange == exchange));
			return trader == null ? exchange.ToExchangeTime(LoggingHelper.Now) : trader.GetMarketTime(exchange);
		}

		/// <summary>
		/// Получить шлюзы <see cref="IInnerTraderList.SortedTraders"/>, отсортированные в зависимости от заданного приоритета. По-умолчанию сортировка отсутствует.
		/// </summary>
		/// <returns>Отсортированные шлюзы.</returns>
		protected virtual IEnumerable<ITrader> GetSortedTraders()
		{
			return _innerTraders.SortedTraders;
		}

		/// <summary>
		/// Подключиться к торговой системе.
		/// </summary>
		public virtual void Connect()
		{
			_innerTraders.SortedTraders.ForEach(t => t.Connect());
		}

		/// <summary>
		/// Отключиться от торговой системы.
		/// </summary>
		public virtual void Disconnect()
		{
			_innerTraders.SortedTraders.ForEach(t => t.Disconnect());
		}

		/// <summary>
		/// Переподключиться к торговой системе.
		/// </summary>
		public virtual void Reconnect()
		{
			_innerTraders.SortedTraders.ForEach(t => t.Reconnect());
		}

		/// <summary>
		/// Запустить экспорт данных из торговой системы в программу (получение портфелей, инструментов, заявок и т.д.).
		/// </summary>
		public virtual void StartExport()
		{
			_innerTraders.SortedTraders.ForEach(t => t.StartExport());
		}

		/// <summary>
		/// Остановить экспорт данных из торговой системы в программу, запущенный через <see cref="ITrader.StartExport"/>.
		/// </summary>
		public virtual void StopExport()
		{
			_innerTraders.SortedTraders.ForEach(t => t.StopExport());
		}

		/// <summary>
		/// Перезапустить экспорт данных из торговой системы через методы <see cref="ITrader.StopExport"/> и <see cref="ITrader.StartExport"/>.
		/// </summary>
		public virtual void ReStartExport()
		{
			_innerTraders.SortedTraders.ForEach(t => t.ReStartExport());
		}

		/// <summary>
		/// Получить позицию по портфелю и инструменту.
		/// </summary>
		/// <param name="portfolio">Портфель, по которому нужно найти позицию.</param>
		/// <param name="security">Инструмент, по которому нужно найти позицию.</param>
		/// <param name="depoName">Название депозитария, где находится физически ценная бумага.
		/// По-умолчанию передается пустая строка, что означает суммарную позицию по всем депозитариям.</param>
		/// <returns>Позиция.</returns>
		public virtual Position GetPosition(Portfolio portfolio, Security security, string depoName = "")
		{
			return _innerTraders.GetTrader(portfolio).GetPosition(portfolio, security, depoName);
		}

		/// <summary>
		/// Получить стакан котировок.
		/// </summary>
		/// <param name="security">Инструмент, по которому нужно получить стакан.</param>
		/// <returns>Стакан котировок.</returns>
		public virtual MarketDepth GetMarketDepth(Security security)
		{
			return GetTrader(security).GetMarketDepth(security);
		}

		/// <summary>
		/// Зарегистрировать заявку на бирже.
		/// </summary>
		/// <param name="order">Заявка, содержащая информацию для регистрации.</param>
		public virtual void RegisterOrder(Order order)
		{
			GetTrader(order).RegisterOrder(order);
		}

		/// <summary>
		/// Перерегистрировать заявку на бирже.
		/// </summary>
		/// <param name="oldOrder">Заявка, которую нужно снять.</param>
		/// <param name="newOrder">Новая заявка, которую нужно зарегистрировать.</param>
		public virtual void ReRegisterOrder(Order oldOrder, Order newOrder)
		{
			GetTrader(oldOrder).ReRegisterOrder(oldOrder, newOrder);
		}

		/// <summary>
		/// Перерегистрировать заявку на бирже.
		/// </summary>
		/// <param name="oldOrder">Заявка, которую нужно снять и на основе нее зарегистрировать новую.</param>
		/// <param name="price">Цена новой заявки.</param>
		/// <param name="volume">Объем новой заявки.</param>
		/// <returns>Новая заявка.</returns>
		public virtual Order ReRegisterOrder(Order oldOrder, decimal price, decimal volume)
		{
			return GetTrader(oldOrder).ReRegisterOrder(oldOrder, price, volume);
		}

		/// <summary>
		/// Отменить заявку на бирже.
		/// </summary>
		/// <param name="order">Заявка, которую нужно отменить.</param>
		public virtual void CancelOrder(Order order)
		{
			GetTrader(order).CancelOrder(order);
		}

		/// <summary>
		/// Отменить группу заявок на бирже по фильтру.
		/// </summary>
		/// <param name="isStopOrder">True, если нужно отменить только стоп-заявки, false - если только обычный и null - если оба типа.</param>
		/// <param name="portfolio">Портфель. Если значение равно null, то портфель не попадает в фильтр снятия заявок.</param>
		/// <param name="direction">Направление заявки. Если значение равно null, то направление не попадает в фильтр снятия заявок.</param>
		/// <param name="classCode">Код класса. Если переданная строка пустая, то код не попадает в фильтр снятия заявок.</param>
		/// <param name="security">Инструмент. Если значение равно null, то инструмент не попадает в фильтр снятия заявок.</param>
		public virtual void CancelOrders(bool? isStopOrder = null, Portfolio portfolio = null, OrderDirections? direction = null, string classCode = null, Security security = null)
		{
			if (portfolio != null)
				_innerTraders.GetTrader(portfolio).CancelOrders(isStopOrder, portfolio, direction, classCode, security);
			else
			{
				foreach (var trader in _innerTraders.SortedTraders)
					trader.CancelOrders(isStopOrder, null, direction, classCode, security);
			}
		}

		/// <summary>
		/// Начать получать котировки (стакан) по инструменту.
		/// Значение котировок можно получить через метод <see cref="M:StockSharp.BusinessEntities.ITrader.GetMarketDepth(StockSharp.BusinessEntities.Security)"/>.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо начать получать котировки.</param>
		public virtual void RegisterMarketDepth(Security security)
		{
			GetTrader(security).RegisterMarketDepth(security);
		}

		/// <summary>
		/// Остановить получение котировок по инструменту.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо остановить получение котировок.</param>
		public virtual void UnRegisterMarketDepth(Security security)
		{
			GetTrader(security).UnRegisterMarketDepth(security);
		}

		/// <summary>
		/// Начать получать сделки (тиковые данные) по инструменту. Новые сделки будут приходить через
		/// событие <see cref="E:StockSharp.BusinessEntities.ITrader.NewTrades"/>.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо начать получать сделки.</param>
		public virtual void RegisterTrades(Security security)
		{
			GetTrader(security).RegisterTrades(security);
		}

		/// <summary>
		/// Остановить получение сделок (тиковые данные) по инструменту.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо остановить получение сделок.</param>
		public virtual void UnRegisterTrades(Security security)
		{
			GetTrader(security).UnRegisterTrades(security);
		}

		/// <summary>
		/// Начать получать новую информацию (например, <see cref="P:StockSharp.BusinessEntities.Security.LastTrade"/> или <see cref="P:StockSharp.BusinessEntities.Security.BestBid"/>) по инструменту.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо начать получать новую информацию.</param>
		public virtual void RegisterSecurity(Security security)
		{
			GetTrader(security).RegisterSecurity(security);
		}

		/// <summary>
		/// Остановить получение новой информации.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо остановить получение новой информации.</param>
		public virtual void UnRegisterSecurity(Security security)
		{
			GetTrader(security).UnRegisterSecurity(security);
		}

		/// <summary>
		/// Начать получать новую информацию по портфелю.
		/// </summary>
		/// <param name="portfolio">Портфель, по которому необходимо начать получать новую информацию.</param>
		public virtual void RegisterPortfolio(Portfolio portfolio)
		{
			_innerTraders.GetTrader(portfolio).RegisterPortfolio(portfolio);
		}

		/// <summary>
		/// Остановить получение новой информации по портфелю.
		/// </summary>
		/// <param name="portfolio">Портфель, по которому необходимо остановить получение новой информации.</param>
		public virtual void UnRegisterPortfolio(Portfolio portfolio)
		{
			_innerTraders.GetTrader(portfolio).UnRegisterPortfolio(portfolio);
		}

		/// <summary>
		/// Начать получать лог заявок для инструмента.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо начать получать лог заявок.</param>
		public virtual void RegisterOrderLog(Security security)
		{
			GetTrader(security).RegisterOrderLog(security);
		}

		/// <summary>
		/// Остановить получение лога заявок для инструмента.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо остановить получение лога заявок.</param>
		public virtual void UnRegisterOrderLog(Security security)
		{
			GetTrader(security).UnRegisterOrderLog(security);
		}

		/// <summary>
		/// Поддерживает ли шлюз инструмент.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <returns>True, если шлюз поддерживает инструмент, иначе, false.</returns>
		public virtual bool IsSupport(Security security)
		{
			return _innerTraders.SortedTraders.Any(t => t.IsSupport(security));
		}

		private ITrader GetTrader(Security security)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			var trader = GetSortedTraders().FirstOrDefault(t => t.IsSupport(security));

			if (trader == null)
				throw new ArgumentException("Не найден подходящий шлюз для инструмента {0}.".Put(security.Id), "security");

			//ThrowIfTraderUnregistered(trader);

			return trader;
		}

		private ITrader GetTrader(Order order)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			return _innerTraders.GetTrader(order.Portfolio);
		}

		/// <summary>
		/// Загрузить настройки.
		/// </summary>
		/// <param name="storage">Хранилище настроек.</param>
		public virtual void Load(SettingsStorage storage)
		{
			_innerTraders.ForEach(t => t.Dispose());
			_innerTraders.Clear();

			SupportUnique = storage.GetValue<bool>("supportUnique");
			SupportTradesUnique = storage.GetValue("supportTradesUnique", true);

			foreach (var traderStorage in storage.GetValue<IEnumerable<SettingsStorage>>("innerTraders"))
			{
				var trader = traderStorage.LoadEntire<ITrader>();

				if (traderStorage.GetValue("IsEmulationMode", false))
					trader = new RealTimeEmulationTrader(trader);

				_innerTraders.Add(trader);
				_innerTraders[trader] = traderStorage.GetValue("priority", -1);
			}
		}

		/// <summary>
		/// Сохранить настройки.
		/// </summary>
		/// <param name="storage">Хранилище настроек.</param>
		public virtual void Save(SettingsStorage storage)
		{
			storage.SetValue("supportUnique", SupportUnique);
			storage.SetValue("supportTradesUnique", SupportTradesUnique);
			storage.SetValue("innerTraders", SaveInnerTraders(_innerTraders.Cache).ToArray());
		}

		/// <summary>
		/// Сохранить настройки вложенных шлюзов.
		/// </summary>
		/// <param name="innerTraders">Вложенные шлюзы.</param>
		/// <returns>Настройки.</returns>
		protected virtual IEnumerable<SettingsStorage> SaveInnerTraders(IEnumerable<ITrader> innerTraders)
		{
			if (innerTraders == null)
				throw new ArgumentNullException("innerTraders");

			return innerTraders.Select(trader =>
			{
				var realTimeEmulationTrader = trader as RealTimeEmulationTrader;
				var traderStorage = realTimeEmulationTrader != null 
					? realTimeEmulationTrader.UnderlyingTrader.SaveEntire() 
					: trader.SaveEntire();

				traderStorage.SetValue("IsEmulationMode", realTimeEmulationTrader != null);
				traderStorage.SetValue("priority", _innerTraders[trader]);

				return traderStorage;
			});
		}

		private void ProcessUnique<TEntity, TId>(IEnumerable<TEntity> newEntities, Func<TEntity, TId> getKey, SynchronizedDictionary<TId, TEntity> cache, Action<IEnumerable<TEntity>> newEntitiesEvent)
		{
			if (SupportUnique)
			{
				var uniqueEntities = new List<TEntity>();

				var entities = newEntities;
				cache.SyncDo(d =>
				{
					foreach (var newEntity in entities)
					{
						var id = getKey(newEntity);

						if (!d.ContainsKey(id))
						{
							d.Add(id, newEntity);
							uniqueEntities.Add(newEntity);
						}
					}
				});

				newEntities = uniqueEntities;
			}

			if (!newEntities.IsEmpty())
				newEntitiesEvent.SafeInvoke(newEntities);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.NewPortfolios"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="portfolios">Новые портфели.</param>
		protected virtual void OnInnerTraderNewPortfolios(ITrader innerTrader, IEnumerable<Portfolio> portfolios)
		{
			ProcessUnique(portfolios, p => p.Name, _portfolios, NewPortfolios);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.PortfoliosChanged"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="portfolios">Измененные портфели.</param>
		protected virtual void OnInnerTraderPortfoliosChanged(ITrader innerTrader, IEnumerable<Portfolio> portfolios)
		{
			PortfoliosChanged.SafeInvoke(portfolios);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.NewPositions"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="positions">Новые позиции.</param>
		protected virtual void OnInnerTraderNewPositions(ITrader innerTrader, IEnumerable<Position> positions)
		{
			NewPositions.SafeInvoke(positions);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.PositionsChanged"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="positions">Измененные позиции.</param>
		protected virtual void OnInnerTraderPositionsChanged(ITrader innerTrader, IEnumerable<Position> positions)
		{
			PositionsChanged.SafeInvoke(positions);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.NewSecurities"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="securities">Новые инструменты.</param>
		protected virtual void OnInnerTraderNewSecurities(ITrader innerTrader, IEnumerable<Security> securities)
		{
			ProcessUnique(securities, s => s.Id, _securities, NewSecurities);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.SecuritiesChanged"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="securities">Измененные инструменты.</param>
		protected virtual void OnInnerTraderSecuritiesChanged(ITrader innerTrader, IEnumerable<Security> securities)
		{
			SecuritiesChanged.SafeInvoke(securities);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.NewTrades"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="trades">Новые сделки.</param>
		protected virtual void OnInnerTraderNewTrades(ITrader innerTrader, IEnumerable<Trade> trades)
		{
			if (SupportTradesUnique)
			{
				var uniqueTrades = trades.ToList();

				_trades.SyncDo(d =>
				{
					foreach (var trade in trades)
					{
						var dict = d.SafeAdd(trade.Security);

						if (dict.ContainsKey(trade.Id))
							uniqueTrades.Remove(trade);
						else
							dict.Add(trade.Id, trade);
					}
				});

				if (!uniqueTrades.IsEmpty())
					NewTrades.SafeInvoke(uniqueTrades);
			}
			else
				NewTrades.SafeInvoke(trades);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.NewMyTrades"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="trades">Новые сделки.</param>
		protected virtual void OnInnerTraderNewMyTrades(ITrader innerTrader, IEnumerable<MyTrade> trades)
		{
			NewMyTrades.SafeInvoke(trades);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.NewOrders"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="orders">Новые заявки.</param>
		protected virtual void OnInnerTraderNewOrders(ITrader innerTrader, IEnumerable<Order> orders)
		{
			NewOrders.SafeInvoke(orders);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.OrdersChanged"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="orders">Измененные заявки.</param>
		protected virtual void OnInnerTraderOrdersChanged(ITrader innerTrader, IEnumerable<Order> orders)
		{
			OrdersChanged.SafeInvoke(orders);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.OrdersRegisterFailed"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="fails">Информация об ошибках.</param>
		protected virtual void OnInnerTraderOrdersRegisterFailed(ITrader innerTrader, IEnumerable<OrderFail> fails)
		{
			OrdersRegisterFailed.SafeInvoke(fails);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.OrdersCancelFailed"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="fails">Информация об ошибках.</param>
		protected virtual void OnInnerTraderOrdersCancelFailed(ITrader innerTrader, IEnumerable<OrderFail> fails)
		{
			OrdersCancelFailed.SafeInvoke(fails);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.NewStopOrders"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="orders">Новые заявки.</param>
		protected virtual void OnInnerTraderNewStopOrders(ITrader innerTrader, IEnumerable<Order> orders)
		{
			NewStopOrders.SafeInvoke(orders);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.StopOrdersChanged"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="orders">Измененные заявки.</param>
		protected virtual void OnInnerTraderStopOrdersChanged(ITrader innerTrader, IEnumerable<Order> orders)
		{
			StopOrdersChanged.SafeInvoke(orders);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.StopOrdersRegisterFailed"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="fails">Информация об ошибках.</param>
		protected virtual void OnInnerTraderStopOrdersRegisterFailed(ITrader innerTrader, IEnumerable<OrderFail> fails)
		{
			StopOrdersRegisterFailed.SafeInvoke(fails);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.StopOrdersCancelFailed"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="fails">Информация об ошибках.</param>
		protected virtual void OnInnerTraderStopOrdersCancelFailed(ITrader innerTrader, IEnumerable<OrderFail> fails)
		{
			StopOrdersCancelFailed.SafeInvoke(fails);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.NewMarketDepths"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="depths">Новые стаканы.</param>
		protected virtual void OnInnerTraderNewMarketDepths(ITrader innerTrader, IEnumerable<MarketDepth> depths)
		{
			NewMarketDepths.SafeInvoke(depths);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.MarketDepthsChanged"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="depths">Измененные стаканы.</param>
		protected virtual void OnInnerTraderMarketDepthsChanged(ITrader innerTrader, IEnumerable<MarketDepth> depths)
		{
			MarketDepthsChanged.SafeInvoke(depths);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.NewOrderLogItems"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="items">Новые строки лога заявок.</param>
		protected virtual void OnInnerTraderNewOrderLogItems(ITrader innerTrader, IEnumerable<OrderLogItem> items)
		{
			NewOrderLogItems.SafeInvoke(items);
		}

		private bool _canRaiseConnected = true;
		private bool _canRaiseDisconnected;

		/// <summary>
		/// Обработчик события <see cref="ITrader.Connected"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		protected virtual void OnInnerTraderConnected(ITrader innerTrader)
		{
			var canProcess = _innerTraders.SyncGet(c =>
			{
				if (_canRaiseConnected && IsConnected)
				{
					_canRaiseConnected = false;
					_canRaiseDisconnected = true;

					return true;
				}

				return false;
			});

			if (canProcess)
				Connected.SafeInvoke();
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.Disconnected"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		protected virtual void OnInnerTraderDisconnected(ITrader innerTrader)
		{
			var canProcess = _innerTraders.SyncGet(c =>
			{
				if (_canRaiseDisconnected)
				{
					var traders = _innerTraders.SortedTraders;

					if (_canRaiseDisconnected && !traders.IsEmpty() && traders.All(t => !t.IsConnected))
					{
						_canRaiseConnected = true;
						_canRaiseDisconnected = false;

						return true;
					}
				}

				return false;
			});

			if (canProcess)
				Disconnected.SafeInvoke();
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.ConnectionError"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="error">Информация об ошибке.</param>
		protected virtual void OnInnerTraderConnectionError(ITrader innerTrader, Exception error)
		{
			lock (_innerTraders.SyncRoot)
			{
				_canRaiseConnected = true;
				_canRaiseDisconnected = false;
			}

			ConnectionError.SafeInvoke(error);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.ProcessDataError"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="error">Информация об ошибке.</param>
		protected virtual void OnInnerTraderProcessDataError(ITrader innerTrader, Exception error)
		{
			ProcessDataError.SafeInvoke(error);
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.NewDataExported"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		protected virtual void OnInnerTraderNewDataExported(ITrader innerTrader)
		{
			NewDataExported.SafeInvoke();
		}

		/// <summary>
		/// Обработчик события <see cref="ITrader.MarketTimeChanged"/> вложенного шлюза.
		/// </summary>
		/// <param name="innerTrader">Вложенный шлюз.</param>
		/// <param name="diff">Разница во времени, прошедшее с последнего вызова события. Первый раз событие передает значение <see cref="TimeSpan.Zero"/>.</param>
		protected virtual void OnInnerTraderMarketTimeChanged(ITrader innerTrader, TimeSpan diff)
		{
			MarketTimeChanged.SafeInvoke(diff);
		}

		/// <summary>
		/// Освободить ресурсы.
		/// </summary>
		protected override void DisposeManaged()
		{
			foreach (var trader in _innerTraders.Cache)
			{
				// AMU: отписаться от событий trader перед Dispose
				// При работе с несколькими Quik'ами возникает проблема с IsConnected в обработчике события BasketTrader.Disconnected - 
				// может быть ситуация когда Dispose происходит между проверкой IsDisposed и последующей проверкой ApiWrapper.IsDllConnected() в QuikTrader.IsConnected
				// Disconnected у BasketTrader вызовется из DisposeManaged() BaseTrader'а
				_innerTraders.UnSubscribe(trader);
				
				trader.Dispose();
			}

			base.DisposeManaged();
		}
	}
}
